"""
实现数据模型的类型约束

问题：
    你想定义某些在属性赋值上面有限制的数据结构。
解决方案：
    在这个问题中，你需要在对某些实例属性赋值时进行检查。 所以你要自定义属性赋值函数，这种情况下最好使用描述器。
"""


class Descriptor:
    def __init__(self, name=None, **kwargs):
        self.name = name
        for key, value in kwargs.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


class Typed(Descriptor):
    expected_type = type(None)

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('expected ' + str(self.expected_type))

        super().__set__(instance, value)


class Unsigned(Descriptor):
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError("Expected >= 0")

        super().__set__(instance, value)


class MaxSized(Descriptor):
    def __init__(self, name=None, **kwargs):
        if 'size' not in kwargs:
            raise TypeError("missing size option")

        super().__init__(name, **kwargs)

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError("size must be < " + str(self.size))
        super().__set__(instance, value)


class Integer(Typed):
    expected_type = int


class UnsignedInteger(Integer, Unsigned):
    pass


class Float(Typed):
    expected_type = float


class UnsignedFloat(Float, Unsigned):
    pass


class String(Typed):
    expected_type = str


class SizedString(String, MaxSized):
    pass


class Stock:
    code = SizedString("code", size=8)
    shares = UnsignedInteger("shares")
    price = UnsignedFloat("price")

    def __init__(self, code, shares, price):
        self.code = code
        self.shares = shares
        self.price = price

    def __str__(self):
        return "({0.code!s}, {0.shares!s}, {0.price!s})".format(self)


def use_stock():
    stock = Stock("GOOG", 100, 1054.7)
    print(stock)
    # 输出结果：
    """
    (GOOG, 100, 1054.7)
    """


if __name__ == '__main__':
    use_stock()
